package com.hirsi.common.module.auth.service;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWTUtil;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.hirsi.common.db.log.entity.LogLogin;
import com.hirsi.common.module.auth.enums.AuthValidEnum;
import com.hirsi.common.core.constant.CommonConstant;
import com.hirsi.common.core.constant.RedisConstant;
import com.hirsi.common.core.exception.ServerRuntimeException;
import com.hirsi.common.core.exception.VoDataValidError;
import com.hirsi.common.core.util.AccessTokenUtil;
import com.hirsi.common.core.util.HttpContextUtil;
import com.hirsi.common.module.auth.enums.LoginTypeEnum;
import com.hirsi.common.module.auth.vo.param.*;
import com.hirsi.common.module.auth.vo.result.CaptchaResult;
import com.hirsi.common.module.auth.vo.result.TokenResult;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class AuthService {

    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IAuthUserService authUserService;

    @Value("${spring.application.name}")
    private String applicationName;

    @Value("${spring.application.id}")
    private Integer applicationId;

    @Value("${ztk.jwt_key}")
    private String jwtKey;

    //正式环境禁止的一些操作
    @Value("${knife4j.production:#{false}}")
    private Boolean isProd;

    @SneakyThrows
    public CaptchaResult captcha() {
        String userAgent = HttpContextUtil.getUserAgent();
        String ipAddr = HttpContextUtil.getIpAddr();
        String tag = SecureUtil.md5(String.format("%s_%s", userAgent, ipAddr));
        String text = defaultKaptcha.createText();
        BufferedImage image = defaultKaptcha.createImage(text);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(image, "PNG", bos);
        byte[] bytes = bos.toByteArray();
        Base64.Encoder encoder = Base64.getEncoder();
        String base64Img = encoder.encodeToString(bytes);
        redisTemplate.opsForValue().set(String.format(RedisConstant.CAPTCHA_KEY_PREFIX, tag), text, RedisConstant.CAPTCHA_EXPIRE_TIME, TimeUnit.SECONDS);
        return new CaptchaResult(tag, String.format("data:image/png;base64,%s", base64Img), RedisConstant.CAPTCHA_EXPIRE_TIME);
    }

    public TokenResult accountLogin(AccountLoginParam param) {
        LogLogin logLogin = new LogLogin().setAccount(param.getAccount()).setApplicationId(applicationId);
        UserAgent userAgent = UserAgentUtil.parse(HttpContextUtil.getUserAgent());
        logLogin.setDevice(userAgent.getPlatform().getName()).setOs(userAgent.getOs().getName())
                .setBrowser(userAgent.getBrowser().getName()).setPlatform(1).setType(LoginTypeEnum.ACCOUNT.getCode())
                .setIp(HttpContextUtil.getIpAddr());
        try {
            String captchaKey = checkCaptcha(param);
            IAuthUser user = authUserService.getUserByAccount(param.getAccount());
            if (user == null) throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.USER_NOT_EXIST));//用户不存在
            if (!param.getPassword().equals(user.getPassword())) throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.PASSWORD_ERROR));//密码错误
            TokenResult result = createToken(user, param.getRemember());
            redisTemplate.delete(captchaKey);
            logLogin.setUserId(user.getId()).setStatus(true);
            return result;
        } catch (Exception e) {
            logLogin.setStatus(false).setErrorMsg(e.getMessage());
            throw e;
        } finally {
            logLogin.insert();
        }
    }

    public void sendSmsCode(SendSmsCodeParam param) {
        String captchaKey = null;
        //如果未登录或手机号不匹配则验证图片验证码
        if (!param.getPhone().equals(AccessTokenUtil.getPhone())) captchaKey = checkCaptcha(param);
        if (Boolean.TRUE.equals(authUserService.checkPhone(param.getPhone()))) {
            //这里发送短信
        } else throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.PHONE_NOT_EXIST));
        if (StrUtil.isNotBlank(captchaKey)) redisTemplate.delete(captchaKey);
    }

    public TokenResult phoneLogin(PhoneLoginParam param) {
        LogLogin logLogin = new LogLogin().setAccount(param.getPhone()).setApplicationId(applicationId);
        UserAgent userAgent = UserAgentUtil.parse(HttpContextUtil.getUserAgent());
        logLogin.setDevice(userAgent.getPlatform().getName()).setOs(userAgent.getOs().getName())
                .setBrowser(userAgent.getBrowser().getName()).setPlatform(1).setType(LoginTypeEnum.PHONE.getCode())
                .setIp(HttpContextUtil.getIpAddr());
        try {
            if (isProd) {//正式环境才验证短信验证码，避免短息浪费
                String code = redisTemplate.opsForValue().get(String.format(RedisConstant.PHONE_COKE_KEY_PREFIX, applicationName, param.getPhone()));
                if (StrUtil.isBlank(code)) throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.CODE_INVALID));
                else if (!param.getCode().equals(code)) throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.CODE_ERROR));
            }
            IAuthUser user = authUserService.getUserByPhone(param.getPhone());
            if (user == null) throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.USER_NOT_EXIST));//用户不存在
            TokenResult result = createToken(user, param.getRemember());
            logLogin.setUserId(user.getId()).setStatus(true);
            return result;
        } catch (Exception e) {
            logLogin.setStatus(false).setErrorMsg(e.getMessage());
            throw e;
        } finally {
            logLogin.insert();
        }
    }

    public void logout() {
        Integer id = AccessTokenUtil.getUserId();
        redisTemplate.delete(String.format(RedisConstant.ACCESS_TOKEN_KEY_PREFIX, applicationName, id));
        redisTemplate.delete(String.format(RedisConstant.REFRESH_TOKEN_KEY_PREFIX, applicationName, id));
    }

    protected String checkCaptcha(CheckCaptchaParam param) {
        if (StrUtil.isBlank(param.getTag())) {
            throw new ServerRuntimeException("验证码唯一标识不能为空");
        } else if (StrUtil.isBlank(param.getCaptcha())) {
            throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.CAPTCHA_ERROR));
        } else if (param.getCaptcha().length() != CommonConstant.CAPTCHA_LENGTH) {
            throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.CAPTCHA_ERROR));
        }
        String captchaKey = String.format(RedisConstant.CAPTCHA_KEY_PREFIX, param.getTag());
        String captcha = redisTemplate.opsForValue().get(captchaKey);
        if (StrUtil.isBlank(captcha)) {
            //图形验证码已失效
            throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.CAPTCHA_INVALID));
        }
        if (!captcha.equals(param.getCaptcha())) {
            //图形验证码错误
            throw new ServerRuntimeException(new VoDataValidError(AuthValidEnum.CAPTCHA_ERROR));
        }
        return captchaKey;
    }

    private TokenResult createToken(IAuthUser user, Boolean remember) {
        JSONObject payload = new JSONObject();
        payload.set("application", applicationName)
                .set("id", user.getId())
                .set("uuid", UUID.randomUUID().toString())
                .set("developerCode", user.getDeveloperCode());
        String accessToken = JWTUtil.createToken(payload, jwtKey.getBytes());
        long accessExpire = Boolean.TRUE.equals(remember) ? RedisConstant.REFRESH_TOKEN_EXPIRE_TIME : RedisConstant.ACCESS_TOKEN_EXPIRE_TIME;
        redisTemplate.opsForValue().set(String.format(RedisConstant.ACCESS_TOKEN_KEY_PREFIX, applicationName, user.getId()), accessToken, accessExpire, TimeUnit.SECONDS);
        TokenResult result = new TokenResult();
        result.setAccessToken(accessToken).setAccessExpire(System.currentTimeMillis() - 5000 + accessExpire * 1000);
        if (Boolean.TRUE.equals(remember)) {
            payload.put("accessToken", accessToken);
            String refreshToken = JWTUtil.createToken(payload, jwtKey.getBytes());
            long refreshExpire = RedisConstant.REFRESH_TOKEN_EXPIRE_TIME;
            redisTemplate.opsForValue().set(String.format(RedisConstant.REFRESH_TOKEN_KEY_PREFIX, applicationName, user.getId()), refreshToken, refreshExpire, TimeUnit.SECONDS);
            result.setRefreshToken(refreshToken).setRefreshExpire(System.currentTimeMillis() - 5000 + refreshExpire * 1000);
        }
        return result;
    }
}
